import java.io.*;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Scanner;

public class Client {
    //用户界面
    public static void main (String args[]) {
        System.out.println("这是一个RSA公钥密码算法实现程序......");
        int status = 0;
        do {
            System.out.println("请选择你需要的功能：1.生成公私钥对；2.加密明文；3.解密密文");
            Scanner choice = new Scanner(System.in);
            String module = choice.nextLine();
            try {
                switch(module) {
                    case "1":  generateKey(); status = 1; choice.close(); break;
                    case "2":  encrypt(); status = 1; choice.close(); break;
                    case "3":  decrypt(); status = 1; choice.close(); break;
                    default : System.out.println("输入错误，请输入1～3选择功能！");
                }
            }catch (Exception e) {System.out.println(e);}
        } while (status == 0);
    }

    //密钥生成程序
    public static void generateKey( ) throws IOException {
        System.out.println("公私钥对生成中......");
        SecureRandom rnd = new SecureRandom();
        BigInteger p = new BigInteger(1024, 80, rnd);  //随机生成1024比特大整数p并使用Miller-Rabin算法进行素性检测，错误率小于2的80次方分之一
        BigInteger q = new BigInteger(1024, 80, rnd);  //随机生成1024比特大整数q并使用Miller-Rabin算法进行素性检测，错误率小于2的80次方分之一
        BigInteger n = p.multiply(q);
        BigInteger phi_n = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
        BigInteger e = new BigInteger("65537");
        BigInteger d = e.modInverse(phi_n);      //求乘法逆元
        String pstring = p.toString();
        String qstring = q.toString();
        String nstring = n.toString();
        String estring = e.toString();
        String dstring = d.toString();
        System.out.println("生成的公钥为：\n"+"n = "+nstring+"\n"+"e = "+estring);
        System.out.println("生成的私钥为：\n"+"p = "+pstring+"\n"+"q = "+qstring+"\n"+"d = "+dstring);
        System.out.println("程序将为您分别导出公钥和私钥，请输入导出路径：（例如  F:\\folder\\）");
        Scanner outputpath = new Scanner(System.in);
        String op = outputpath.nextLine();
        PrintStream publickey = new PrintStream(op + "publickey.txt");
        PrintStream privatekey = new PrintStream(op + "privatekey.txt");
        publickey.println(nstring);
        publickey.println();
        publickey.println(estring);
        privatekey.println(pstring);
        privatekey.println();
        privatekey.println(qstring);
        privatekey.println();
        privatekey.println(dstring);
        System.out.println("公私钥对导出成功！默认文件名为publickey.txt和privatekey.txt ，请前往查看。");
        outputpath.close();
        publickey.close();
        privatekey.close();
    }

    //加密程序
    public static void encrypt() throws IOException {
        System.out.println("请输入公钥文件的路径和文件名：（例如  F:\\folder\\publickey.txt）");
        Scanner input1 = new Scanner(System.in);
        String pukpath = input1.nextLine();
        File  pukfile = new File(pukpath);
        Scanner puk = new Scanner(pukfile);
        BigInteger n = puk.nextBigInteger();
        BigInteger e = puk.nextBigInteger();
        System.out.println("公钥导入成功!");
        System.out.println("n = "+ n.toString());
        System.out.println("e = "+ e.toString());
        System.out.println("\n请输入要加密的内容：（允许输入中文）");
        Scanner input2 = new Scanner(System.in);
        String plaintext = input2.nextLine();
        byte[] ptbyte = plaintext.getBytes();
        BigInteger m = new BigInteger(ptbyte);
        boolean flag = false;
        if (m.compareTo(BigInteger.ZERO) < 0 ) { flag = true; }
        String mstring = m.toString();
        System.out.println("加密成功!");
        System.out.println("明文对应的数值 m = "+ mstring);
        if (flag == true) {System.out.println("（明文数值开头为负号表明明文以非英文字符开头，不影响加解密）");}
        m = m.abs();
        BigInteger c = m.modPow(e, n);    //快速指数算法
        String cstring;
        if (flag == false) {cstring = c.toString();}
        else {cstring = "-"+c.toString();}
        System.out.println("密文对应的数值 c = "+ cstring);
        if (flag == true) {System.out.println("（密文数值开头的负号用于标识明文以非英文字符开头，不影响加解密）");}
        System.out.println("程序将为您导出密文，请输入导出路径：（例如  F:\\folder\\）");
        Scanner outputpath = new Scanner(System.in);
        String op = outputpath.nextLine();
        PrintStream ciphertext = new PrintStream(op + "ciphertext.txt");
        if (flag == false) {ciphertext.println(c);}
        else {ciphertext.println(c.negate());}
        System.out.println("密文导出成功！默认文件名为ciphertext.txt ，请前往查看。");
        input1.close();
        input2.close();
        puk.close();
        outputpath.close();
        ciphertext.close();
    }

    //解密程序
    public static void decrypt() throws IOException {
        System.out.println("请输入私钥文件的路径和文件名：（例如  F:\\folder\\privatekey.txt）");
        Scanner input1 = new Scanner(System.in);
        String prkpath = input1.nextLine();
        File  prkfile = new File(prkpath);
        Scanner prk = new Scanner(prkfile);
        BigInteger p = prk.nextBigInteger();
        BigInteger q = prk.nextBigInteger();
        BigInteger d = prk.nextBigInteger();
        System.out.println("私钥导入成功!");
        System.out.println("p = "+ p.toString());
        System.out.println("q = "+ q.toString());
        System.out.println("d = "+ d.toString());
        System.out.println("请输入密文文件的路径和文件名：（例如  F:\\folder\\ciphertext.txt）");
        Scanner input2 = new Scanner(System.in);
        String ctpath = input2.nextLine();
        File  ctfile = new File(ctpath);
        Scanner ct = new Scanner(ctfile);
        BigInteger c = ct.nextBigInteger();
        System.out.println("密文导入成功!");
        System.out.println("c = "+ c.toString());
        boolean flag = false;
        if (c.compareTo(BigInteger.ZERO) < 0 )  {flag = true;}
        if (flag == true) {System.out.println("（密文数值开头的负号用于标识明文以非英文字符开头，不影响加解密）");}
        c = c.abs();
        BigInteger n = p.multiply(q);
        BigInteger decryptNum = c.modPow(d, n);    //快速指数算法
        if (flag == true) {decryptNum = decryptNum.negate();}
        byte[] decryptByte = decryptNum.toByteArray();
        String decryptText = new String(decryptByte);
        System.out.println("\n解密成功！得到的明文为：\n"+ decryptText);
        input1.close();
        input2.close();
        prk.close();
        ct.close();
    }
}